﻿using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Threading;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Xml.Serialization;
using Kokomo.Environment;
using Kokomo.Environment.Commanding;
using Kokomo.Environment.Hosting;
using Kokomo.Environment.Hosting.Services;
using Kokomo.Environment.Services;
using Kokomo.Mirror.CliMetadata;
using Kokomo.Mirror.PeFormat;
using Kokomo.PeInspector.Settings;

namespace Kokomo.PeInspector.Reflection
{
	[Export(typeof(DocumentEditor))]
	public class AssemblyAnalyzerEditor : DocumentEditor
	{
		public static readonly FileType AssemblyFileType = new FileType(UiString.FromString(Messages.AssemblyFileType), new string[] { "*.exe", "*.dll", "*.sys", "*.cpl", "*.mcl" });
		public static readonly FileType AssemblyListFileType = new FileType(UiString.FromString(Messages.AssemblyListFileType), new string[] { "*.assemblylist" });
		public static readonly FileType CSharpFileType = new FileType(UiString.FromString(Messages.CSharpFileType), new string[] { "*.cs" });
		public static readonly FileType FlowDocumentFileType = new FileType(UiString.FromString(Messages.FormattedFlowDocumentFileType), new string[] { "*.xaml" });
		public static readonly FileType XamlFileType = new FileType(UiString.FromString(Messages.XxamlDocumentFileType), new string[] { "*.xaml" });
		public static readonly FileType MsilFileType = new FileType(UiString.FromString(Messages.MsilFileType), new string[] { "*.msil" });
		public static readonly FileType AssemblyListingFileType = new FileType(UiString.FromString(Messages.AssemblyListingFileType), new string[] { "*.asm" });

		public static readonly string ProjectUrl = "http://asmanalyzer.codeplex.com/";
		public static readonly string ProjectDocumentationUrl = "http://asmanalyzer.codeplex.com/documentation";

		public static string[] AssemblyInfoNamespaceImports { get; private set; }

		public static string[] GetSourceDocumentNamespaceImports(Assembly assembly)
		{
			if (assembly == null) throw new ArgumentNullException("assembly");

			List<string> namespaceImports = new List<string>(4) {
				"System",
				"System.Collections.Generic",
				"System.Text"
			};

			AssemblyName[] references = assembly.GetReferencedAssemblies();
			foreach (var reference in references)
			{
				if (reference.Name == "System.Core")
				{
					namespaceImports.Add("System.Linq");
				}
			}

			namespaceImports.Sort();

			return namespaceImports.ToArray();
		}

		static AssemblyAnalyzerEditor()
		{
			AssemblyInfoNamespaceImports = new string[] {
				"System",
				"System.Diagnostics",
				"System.Reflection",
				"System.Runtime",
				"System.Runtime.CompilerServices",
				"System.Runtime.InteropServices",
				"System.Runtime.Versioning"
			};

			InitializeMetadataIconProvider();

			// Settings
			ISettingsManager settingsManager = EnvironmentServices.GetService<ISettingsManager>();
			if (settingsManager != null)
			{
				ViewSettings = settingsManager.GetSettings<ViewSettings>();
				SourceViewSettings = settingsManager.GetSettings<SourceViewSettings>();
			}

			// Commands
			ViewOnlineDocumentationCommand = new DelegatedCommand(ViewOnlineDocumentation);

			ShowAboutInfoCommand = new DelegatedCommand(ShowAboutInfo);
			HelpMenuItemViewModels = new MenuItemViewModel[] {
				new MenuViewModel(UiString.FromString(Environment.Messages.HelpMenuTitle), new MenuItemViewModel[] {
					new MenuCommandItemViewModel(new CommandViewModel(AssemblyAnalyzerEditor.ViewOnlineDocumentationCommand, UiString.FromString(Environment.Messages.ViewOnlineDocumentationMenuTitle))),
					new MenuCommandItemViewModel(new CommandViewModel(AssemblyAnalyzerEditor.ShowAboutInfoCommand, UiString.FromString(Environment.Messages.AboutMenuTitle)))
				})
			};

			// Dependency index
			string databaseDirectory = Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName);
			//string databaseDirectory = Path.Combine(System.Environment.GetFolderPath(System.Environment.SpecialFolder.ApplicationData, @"Assembly Analyzer\Cache");
			string databaseFileName = Path.Combine(databaseDirectory, "AssemblyIndexCache.sdf");

			//AssemblyIndexCache cache = File.Exists(databaseFileName)
			//    ? new AssemblyIndexCache(databaseFileName)
			//    : AssemblyIndexCache.Create(databaseFileName)
			//    ;
			//DependencyCache = cache;

			// Code text format
			InitializeCodeTextFormats();
		}

		//internal static AssemblyIndexCache DependencyCache { get; private set; }

		public static IImageProvider MetadataIconProvider { get; private set; }

		private static void InitializeMetadataIconProvider()
		{
			Assembly assembly = typeof(AssemblyAnalyzerEditor).Assembly;
			ImageManager images = new ImageManager();

			string[] imageKeys = new string[] {
				MetadataIconKeys.Assembly,
				MetadataIconKeys.Namespace,
				MetadataIconKeys.Class,
				MetadataIconKeys.PrivateClass,
				MetadataIconKeys.FamilyClass,
				MetadataIconKeys.AssemblyClass,
				MetadataIconKeys.Enum,
				MetadataIconKeys.PrivateEnum,
				MetadataIconKeys.FamilyEnum,
				MetadataIconKeys.AssemblyEnum,
				MetadataIconKeys.Delegate,
				MetadataIconKeys.PrivateDelegate,
				MetadataIconKeys.FamilyDelegate,
				MetadataIconKeys.AssemblyDelegate,
				MetadataIconKeys.Struct,
				MetadataIconKeys.PrivateStruct,
				MetadataIconKeys.FamilyStruct,
				MetadataIconKeys.AssemblyStruct,
				MetadataIconKeys.Interface,
				MetadataIconKeys.PrivateInterface,
				MetadataIconKeys.FamilyInterface,
				MetadataIconKeys.AssemblyInterface,
				MetadataIconKeys.Method,
				MetadataIconKeys.PrivateMethod,
				MetadataIconKeys.FamilyMethod,
				MetadataIconKeys.AssemblyMethod,
				MetadataIconKeys.Event,
				MetadataIconKeys.PrivateEvent,
				MetadataIconKeys.FamilyEvent,
				MetadataIconKeys.AssemblyEvent,
				MetadataIconKeys.Field,
				MetadataIconKeys.PrivateField,
				MetadataIconKeys.FamilyField,
				MetadataIconKeys.AssemblyField,
				MetadataIconKeys.Property,
				MetadataIconKeys.PrivateProperty,
				MetadataIconKeys.FamilyProperty,
				MetadataIconKeys.AssemblyProperty,

				ImageKeys.CSharpCode,
				ImageKeys.MsilCode,
				ImageKeys.Xaml,
				ImageKeys.AssemblyReferences,
				ImageKeys.String,
				ImageKeys.Sections,
				ImageKeys.Section,
				ImageKeys.Symbols,
				ImageKeys.Symbol,
				ImageKeys.Table,
			};

			foreach (var imageKey in imageKeys)
			{
				string resourcePath = string.Format("images/{0}.png", imageKey);
				images.AddImageResource(imageKey, resourcePath, assembly);
			}

			MetadataIconProvider = images;
		}

		public AssemblyAnalyzerEditor()
		{
			this.SupportedFileTypes = new FileType[] {
				AssemblyFileType,
				AssemblyListFileType
			};
			XmlSerializer serializer = new XmlSerializer(typeof(AssemblyList));
			this.Serializer = serializer;
		}

		[Export(DelegatedDocumentTemplate.ContractName)]
		[ExportMetadata(DelegatedDocumentTemplate.TitlePropertyName, "Assembly List")]
		public DocumentViewModel CreateAssemblyList()
		{
			IAssemblyLoadContext loadContext = CreateAssemblyLoadContext();
			AssemblyList list = new AssemblyList();
			list.Attach(loadContext);
			return new AssemblyListDocumentViewModel(list);
		}

		public static IAssemblyLoadContext CreateAssemblyLoadContext(string searchPath = null)
		{
			IAssemblyLoadContext context;
			if (Keyboard.Modifiers.HasFlag(ModifierKeys.Shift))
				context = new ReflectionLoadContext();
			else
				context = new MirrorLoadContext(searchPath);

			return context;
			//return new ReflectionOnlyLoadContext();
			//return new ReflectionLoadContext();
		}

		public override string Title
		{
			get { return Messages.AssemblyAnalyzerEditorTitle; }
		}
		public override DocumentViewModel CreateNew()
		{
			return this.CreateAssemblyList();
		}

		public XmlSerializer Serializer { get; private set; }
		public override DocumentViewModel OpenExisting(string path)
		{
			if (path == null) throw new ArgumentNullException("path");

			DocumentViewModel viewModel = null;
			if (AssemblyListFileType.Matches(path))
			{
				viewModel = this.OpenExistingList(path);
			}
			else  // Don't verify extension
			{
				viewModel = this.OpenExistingModule(path);
			}

			return viewModel;
		}
		protected DocumentViewModel OpenExistingList(string path)
		{
			if (path == null) throw new ArgumentNullException("path");

			AssemblyList assemblyList;
			using (FileStream stream = new FileStream(path, FileMode.Open, FileAccess.Read))
			{
				assemblyList = (AssemblyList)this.Serializer.Deserialize(stream);
			}

			IAssemblyLoadContext loadContext = CreateAssemblyLoadContext();
			assemblyList.AttachAsync(loadContext);
			AssemblyListDocumentViewModel document = new AssemblyListDocumentViewModel(assemblyList);
			if (assemblyList.Favorites != null)
			{
				foreach (var favorite in assemblyList.Favorites)
				{
					try
					{
						Assembly favoriteAssembly = loadContext.LoadAssembly(new AssemblyName(favorite.AssemblyDisplayName));
						Module module = favoriteAssembly.ManifestModule;

						if (favorite.MemberToken.HasValue)
						{
							MemberInfo member = module.ResolveMember(favorite.MemberToken.Value);
							document.AddFavorite(member);
						}
						else
						{
							document.AddFavorite(favoriteAssembly);
						}
					}
					catch { }
				}
			}

			return document;
		}
		public static bool IsAssembly(string path)
		{
			if (!File.Exists(path)) return false;

			PeModule module = PeModule.Open(path);
			return module.IsCliAssembly;
		}
		protected DocumentViewModel OpenExistingModule(string path)
		{
			if (path == null) throw new ArgumentNullException("path");

			if (IsAssembly(path))
			{
				// Load assembly
				IAssemblyLoadContext loadContext = CreateAssemblyLoadContext(Path.GetDirectoryName(path));
				Assembly assembly = loadContext.LoadAssemblyFile(path);
				AssemblyList assemblyList = new AssemblyList();
				assemblyList.Attach(loadContext);
				assemblyList.AddAssemblyWithReferencesAsync(assembly);

				// Create and add view model
				AssemblyListDocumentViewModel assemblyListViewModel = new AssemblyListDocumentViewModel(assemblyList);
				assemblyListViewModel.Title = UiString.FromString(Path.GetFileNameWithoutExtension(path));

				EnvironmentServices.DocumentManager.ShowDocument(assemblyListViewModel);

				return null;
			}
			else
			{
				// TODO: Verify PE signatures

				PeModule module = PeModule.Open(path);
				PeModuleViewModel viewModel = new PeModuleViewModel(module)
				{
					Title = UiString.FromString(Path.GetFileName(path))
				};
				return viewModel;
			}
		}

		public static ImageSource GetIconForModule(string fileName)
		{
			if (fileName == null) throw new ArgumentNullException("fileName");

			if (Application.Current.Dispatcher.Thread != Thread.CurrentThread)
			{
				return (ImageSource)Application.Current.Dispatcher.Invoke(
					(Func<string, ImageSource>)GetIconForModule,
					fileName
					);
			}
			else
			{
				var win32Icon = System.Drawing.Icon.ExtractAssociatedIcon(fileName);
				ImageSource iconSource = System.Windows.Interop.Imaging.CreateBitmapSourceFromHIcon(
					win32Icon.Handle,
					new System.Windows.Int32Rect(),
					BitmapSizeOptions.FromWidthAndHeight(16, 16)
					);
				return iconSource;
			}
		}

		#region Help
		public static IEnumerable<MenuItemViewModel> HelpMenuItemViewModels { get; private set; }

		public static ICommand ViewOnlineDocumentationCommand { get; private set; }
		public static void ViewOnlineDocumentation()
		{
			Process.Start(ProjectDocumentationUrl);
		}

		public static ICommand ShowAboutInfoCommand { get; private set; }
		public static void ShowAboutInfo()
		{
			AboutDocumentViewModel about = new AboutDocumentViewModel(typeof(AssemblyAnalyzerEditor).Assembly, ProjectUrl);
			EnvironmentServices.DocumentManager.ShowDocument(about);
		}
		#endregion

		#region Settings
		internal static ViewSettings ViewSettings { get; private set; }
		internal static SourceViewSettings SourceViewSettings { get; private set; }

		internal static CodeTextFormatItem[] CodeTextFormatItems { get; private set; }

		private static void InitializeCodeTextFormats()
		{
			CodeTextFormatItems = new CodeTextFormatItem[] {
				new CodeTextFormatItem(CodeTextType.Normal, "Normal") { Color = Colors.Black },
				new CodeTextFormatItem(CodeTextType.Keyword, "Keyword") { Color = Colors.Blue },
				new CodeTextFormatItem(CodeTextType.Operator, "Operator") { Color = Colors.Gray },
				new CodeTextFormatItem(CodeTextType.Annotation, "Annotation") { Color = Colors.Gray },
				new CodeTextFormatItem(CodeTextType.Comment, "Comment") { Color = Colors.Green },
				new CodeTextFormatItem(CodeTextType.TypeName, "Type name") { Color = Colors.Teal },
				new CodeTextFormatItem(CodeTextType.Literal, "Literal") { Color = Colors.Maroon },
				new CodeTextFormatItem(CodeTextType.Declarator, "Declarator") { Color = Colors.Black, FontWeight = FontWeights.Bold },
				new CodeTextFormatItem(CodeTextType.XmlElementName, "XML element name") { Color = Colors.Maroon },
				new CodeTextFormatItem(CodeTextType.XmlEntityName, "XML entity name") { Color = Colors.Maroon },
				new CodeTextFormatItem(CodeTextType.XmlAttributeName, "XML attribute name") { Color = Colors.Red },
				new CodeTextFormatItem(CodeTextType.XmlAttributeValue, "XML attribute value") { Color = Colors.Blue },
			};
		}
		#endregion
	}
}
